home *** CD-ROM | disk | FTP | other *** search
/ Developer CD Series 2000 November: Tool Chest / Dev.CD Nov 00 TC Disk 1.toast / Sample Code / Networking / OTStreamLogViewer / OTStreamLogViewer.c < prev    next >
Encoding:
C/C++ Source or Header  |  2000-09-28  |  56.4 KB  |  1,925 lines  |  [TEXT/CWIE]

  1. /*
  2.     File:        OTStreamLogViewer.c
  3.  
  4.     Contains:    A program to display information logged to the STREAMS log module.
  5.  
  6.     Written by: Quinn "The Eskimo!"    
  7.  
  8.     Copyright:    Copyright Â© 1998-1999 by Apple Computer, Inc., All Rights Reserved.
  9.  
  10.                 You may incorporate this Apple sample source code into your program(s) without
  11.                 restriction. This Apple sample source code has been provided "AS IS" and the
  12.                 responsibility for its operation is yours. You are not permitted to redistribute
  13.                 this Apple sample source code as "Apple sample source code" after having made
  14.                 changes. If you're going to re-distribute the source, we require that you make
  15.                 it clear in the source that the code was descended from Apple sample source
  16.                 code, but that you've made changes.
  17.  
  18.     Change History (most recent first):
  19.                 7/23/1999    Karl Groethe    Updated for Metrowerks Codewarror Pro 2.1
  20.                 
  21.  
  22. */
  23.  
  24. #define qDebug 1
  25.  
  26. /////////////////////////////////////////////////////////////////////
  27. // Standard C stuff.
  28.  
  29. #include <stdio.h>
  30.  
  31. /////////////////////////////////////////////////////////////////////
  32. // Pick up standard OT APIs.
  33.  
  34. #include <OpenTransport.h>
  35.  
  36. /////////////////////////////////////////////////////////////////////
  37. // Pick up the low-level OT APIs.
  38.  
  39. #include <OTDebug.h>
  40.  
  41. /////////////////////////////////////////////////////////////////////
  42. // Pick up standard toolbox APIs.
  43.  
  44. #include <QuickDraw.h>
  45. #include <Fonts.h>
  46. #include <Windows.h>
  47. #include <Menus.h>
  48. #include <TextEdit.h>
  49. #include <Dialogs.h>
  50. #include <Memory.h>
  51. #include <Resources.h>
  52. #include <TextUtils.h>
  53. #include <Scrap.h>
  54. #include <Devices.h>
  55. #include <CodeFragments.h>
  56. #include <Gestalt.h>
  57. #include <Processes.h>
  58. #include <Lists.h>
  59. /////////////////////////////////////////////////////////////////////
  60. // Pick up less-standard toolbox APIs.
  61.  
  62. #include "InternetConfig.h"
  63.  
  64.  
  65. /////////////////////////////////////////////////////////////////////
  66. // Lots of useful subroutines from the Internet Config libraries.
  67. #include "ICDialogs.h"
  68. #include "ICMiscSubs.h"
  69. #include "ICCommonSubs.h"
  70.  
  71.  
  72. /////////////////////////////////////////////////////////////////////
  73. // Pick up our resource definitions.
  74.  
  75. #include "StreamLogResources.h"
  76.  
  77. /////////////////////////////////////////////////////////////////////
  78. // Pick up the file logging stuff.
  79.  
  80. #include "FileLogging.h"
  81.  
  82. /////////////////////////////////////////////////////////////////////
  83. // Pick up the log engine prototypes.
  84.  
  85. #include "LogEngine.h"
  86.  
  87. /////////////////////////////////////////////////////////////////////
  88. // OTDebugStr is not defined in any OT header files, but it is
  89. // exported by the libraries, so we define the prototype here.
  90.  
  91. extern pascal void OTDebugStr(const char* str);
  92.  
  93. /////////////////////////////////////////////////////////////////////
  94. #pragma mark **** Applications Globals *****
  95.  
  96. enum {
  97.     kCreator = 'SlVw'
  98. };
  99.  
  100. static Boolean gQuitNow;
  101.     // Set to true to drop out of the main event loop.
  102.     
  103. static DialogPtr gMainWindow = nil;
  104.     // The main window.  Pretty much the only window at
  105.     // the moment.
  106.     
  107. static ListHandle gLogList = nil;
  108.     // The List Manager list in the main window.
  109.  
  110. static ListDefUPP gListDefProcUPP;
  111.     // A UPP for the list definition proc we use
  112.     // for this list.  We have a dummy LDEF whose
  113.     // resource ID we supply to LNew.  That LDEF just
  114.     // calls through the list's refCon (assuming it isn't
  115.     // nil).  We poke this UPP into that refCon, and hence
  116.     // the LDEF calls us.  This allows for source
  117.     // level debugging of the LDEF without messing around
  118.     // with self-modifying code.
  119.  
  120. static UserItemUPP gListUserItemUpdateUPP;
  121.     // A UPP for the user item in gMainWindow that contains
  122.     // the gLogList.  This routine simply calls LUpdate
  123.     // (oh, and also draws the frame for the list).
  124.  
  125. static Rect gMainWindowGrowBounds;
  126.     // Bounds which we pass to GrowWindow to constrain how
  127.     // much the window can grow or shrink.  The topLeft
  128.     // co-ordinate in the minimum size (which we get from
  129.     // the window's portRect just after we create it)
  130.     // and the botRight are derived from screenBits.bounds
  131.     // (which Window Manager special cases to allow windows
  132.     // to grow as large as possible on the screen(s)).
  133.     
  134. static Boolean gScrollWhileLogging = true;
  135.     // This value is a mirror of the state of the menu
  136.     // item.  We mirror it to make it quicker to fetch while
  137.     // we're logging.
  138.     
  139. static SInt16 gModuleID;                        // -1 => log all modules
  140. static SInt16 gStreamID;                        // -1 => log all streams
  141.     // These are the logging filters.  This only take effect
  142.     // if the All Traces radio button is /not/ on.  The values
  143.     // are set by means of a modal dialog.
  144.  
  145. static void DirtyPreferences(void);
  146.     // forward
  147. static void PreferencesIdle(void);
  148.     // forward
  149.  
  150. /////////////////////////////////////////////////////////////////////
  151. #pragma mark **** Apple Event Handlers *****
  152.  
  153. static pascal OSErr AEOpenApplicationHandler(const AppleEvent *theAppleEvent, AppleEvent *reply, long handlerRefcon)
  154. {
  155.     #pragma unused(reply)
  156.     #pragma unused(handlerRefcon)
  157.     OSStatus err;
  158.     
  159.     err = AEGotRequiredParams(theAppleEvent);
  160.     return err;
  161. }
  162.  
  163. static pascal OSErr AEOpenDocumentsHandler(const AppleEvent *theAppleEvent, AppleEvent *reply, long handlerRefcon)
  164. {
  165.     #pragma unused(reply)
  166.     #pragma unused(handlerRefcon)
  167.     OSStatus err;
  168.     
  169.     err = AEGotRequiredParams(theAppleEvent);
  170.     return err;
  171. }
  172.  
  173. static pascal OSErr AEQuitApplicationHandler(const AppleEvent *theAppleEvent, AppleEvent *reply, long handlerRefcon)
  174. {
  175.     #pragma unused(reply)
  176.     #pragma unused(handlerRefcon)
  177.     OSStatus err;
  178.     
  179.     err = AEGotRequiredParams(theAppleEvent);
  180.     if (err == noErr) {
  181.         gQuitNow = true;
  182.     }
  183.     return err;
  184. }
  185.  
  186. /////////////////////////////////////////////////////////////////////
  187. #pragma mark **** List Logging Stuff *****
  188.  
  189. enum {
  190.  
  191.     // kMaxListRows is the maximum number of rows we can have in our
  192.     // scrolling list.  List Manager imposes an overall limit of 32K.
  193.     // It also imposes a 32K limit on the amount of data in the list.
  194.     // We use 4 bytes per row of data, which would imply this limit
  195.     // should be 8K.  5000 is just to keep it within reasonable bounds.
  196.     
  197.     kMaxListRows = 5000,
  198.     
  199.     // When the list exceeds the above limit, we delete some rows from
  200.     // the front of the list.  We do this in chunks for efficiency's
  201.     // sake.  This is the size of the chunk we delete.
  202.     
  203.     kNumberOfRowsToDelete = 100
  204. };
  205.  
  206. static LogEntryPtr ListGetLogEntryForRow(SInt16 rowNumber)
  207.     // Given a row number (zero based), this routine returns
  208.     // the log entry for that row.  if there is no log entry
  209.     // (which can happen if the routine is called by the LDEF
  210.     // as the row is added), it returns nil.
  211. {
  212.     Cell thisCell;
  213.     LogEntryPtr result;
  214.     SInt16 dataSize;
  215.     
  216.     thisCell.h = 0;
  217.     thisCell.v = rowNumber;
  218.     dataSize = sizeof(result);
  219.     LGetCell(&result, &dataSize, thisCell, gLogList);
  220.     if ( dataSize != sizeof(result) ) {
  221.         result = nil;
  222.     }
  223.     return result;
  224. }
  225.  
  226. static void ListSetLogEntryForRow(SInt16 rowNumber, LogEntryPtr rowEntry)
  227.     // This routine sets the data for the given rowNumber (zero based)
  228.     // to rowEntry, to later be retrieved by ListGetLogEntryForRow.
  229. {
  230.     Cell thisCell;
  231.     
  232.     thisCell.h = 0;
  233.     thisCell.v = rowNumber;
  234.     LSetCell(&rowEntry, sizeof(rowEntry), thisCell, gLogList);
  235. }
  236.  
  237. static void FlagsToShortString(char flags, Str255 flagStr)
  238.     // This routine sets flagStr to an 8 character string
  239.     // that represents flags in a very compact form, suitable
  240.     // for drawing by the LDEF.
  241. {
  242.     OTMemset(&flagStr[1], '-', 8);
  243.     flagStr[0] = 8;
  244.     
  245.     if ((flags & SL_TRACE) != 0) {
  246.         flagStr[1] = 'T';
  247.     }
  248.     if ((flags & SL_ERROR) != 0) {
  249.         flagStr[2] = 'E';
  250.     }
  251.     if ((flags & SL_CONSOLE) != 0) {
  252.         flagStr[3] = 'C';
  253.     }
  254.     
  255.     // flagStr[4] is blank
  256.     
  257.     if ((flags & SL_FATAL) != 0) {
  258.         flagStr[5] = 'F';
  259.     }
  260.     if ((flags & SL_NOTIFY) != 0) {
  261.         flagStr[6] = 'N';
  262.     }
  263.     if ((flags & SL_WARN) != 0) {
  264.         flagStr[7] = 'W';
  265.     }
  266.     if ((flags & SL_NOTE) != 0) {
  267.         flagStr[8] = 'N';
  268.     }
  269. }
  270.  
  271. // These constants define the start (and hence the width)
  272. // of each of the fields in our LDEF.
  273.  
  274. enum {
  275.     kFieldStart0 = 4,
  276.     kFieldStart1 = kFieldStart0 + 40,
  277.     kFieldStart2 = kFieldStart1 + 60,
  278.     kFieldStart3 = kFieldStart2 + 60,
  279.     kFieldStart4 = kFieldStart3 + 60,
  280.     kFieldStart5 = kFieldStart4 + 40,
  281.     kFieldStart6 = kFieldStart5 + 40,
  282.     kFieldStart7 = kFieldStart6 + 0
  283. };
  284.  
  285. static void ListDefProcDraw(Rect *theCellRect, SInt16 rowNumber)
  286.     // This routine is called by the LDEF in response to an
  287.     // lDraw message.  The basic idea is to get the log entry
  288.     // for the given row and then use the given rectangle to
  289.     // render the data in the entry.
  290. {
  291.     LogEntryPtr thisEntry;
  292.     Str255 tmpStr;
  293.     UInt32 strLen;
  294.  
  295.     thisEntry = ListGetLogEntryForRow(rowNumber);
  296.     OTAssert("ListDefProc: No data for cell", thisEntry != nil);
  297.     if (thisEntry != nil) {
  298.  
  299.         NumToString(thisEntry->fLogHeader.seq_no, tmpStr);
  300.         MoveTo(theCellRect->left + kFieldStart0, theCellRect->bottom - 4);
  301.         DrawString(tmpStr);
  302.  
  303.         MoveTo(theCellRect->left + kFieldStart1, theCellRect->bottom - 4);
  304.         FlagsToShortString(thisEntry->fLogHeader.flags, tmpStr);
  305.         DrawString(tmpStr);
  306.  
  307.         MoveTo(theCellRect->left + kFieldStart2, theCellRect->bottom - 4);
  308.         DateString(thisEntry->fLogHeader.ttime, shortDate, tmpStr, nil);
  309.         DrawString(tmpStr);
  310.  
  311.         TimeString(thisEntry->fLogHeader.ttime, true, tmpStr, nil);
  312.         MoveTo(theCellRect->left + kFieldStart3, theCellRect->bottom - 4);
  313.         DrawString(tmpStr);
  314.         
  315.         NumToString(thisEntry->fLogHeader.mid, tmpStr);
  316.         MoveTo(theCellRect->left + kFieldStart4, theCellRect->bottom - 4);
  317.         DrawString(tmpStr);
  318.  
  319.         NumToString(thisEntry->fLogHeader.sid, tmpStr);
  320.         MoveTo(theCellRect->left + kFieldStart5, theCellRect->bottom - 4);
  321.         DrawString(tmpStr);
  322.  
  323.         // We clip strLen at 255 to avoid attempting to draw more than
  324.         // a 255 character string (which won't work 'cause it's a Pascal
  325.         // string).  We could have used DrawText, but 255 characters is
  326.         // enough for me.  If you need more, look at the file log.
  327.         
  328.         strLen = thisEntry->fTextLength;
  329.         if ( strLen > 255 ) {
  330.             strLen = 255;
  331.         }
  332.         BlockMoveData((char *) thisEntry + sizeof(LogEntry), &tmpStr[1], strLen);
  333.         tmpStr[0] = strLen;
  334.         MoveTo(theCellRect->left + kFieldStart6, theCellRect->bottom - 4);
  335.         DrawString(tmpStr);
  336.     }
  337. }
  338.  
  339. static pascal void ListDefProc(SInt16 message, Boolean drawSelected,
  340.                                 Rect *theCellRect, Cell theCell, 
  341.                                 SInt16 dataOffset, SInt16 dataLen, 
  342.                                 ListRef listH)
  343.     // Our list definition proc (LDEF).  This routine is called
  344.     // back by the List Manager when it wants to draw (or highlight)
  345.     // a cell in the list in our main window.  The implementation
  346.     // basically dispatches based on message.  Of cours, there's only
  347.     // one basic action, so this is just a glorified wrapper for
  348.     // ListDefProcDraw.
  349. {
  350.     #pragma unused(dataOffset)
  351.     #pragma unused(dataLen)
  352.  
  353.     #if qDebug
  354.         OTAssert("ListDefProc: This LDEF only works for gLogList", listH == gLogList);
  355.     #else
  356.         #pragma unused(listH)
  357.     #endif
  358.     
  359.     switch (message) {
  360.         case lInitMsg:
  361.             // do nothing
  362.             break;
  363.         case lDrawMsg:
  364.         case lHiliteMsg:
  365.             EraseRect(theCellRect);
  366.             ListDefProcDraw(theCellRect, theCell.v);
  367.             if (drawSelected) {
  368.                 MagicMarkerMode();
  369.                 InvertRect(theCellRect);
  370.             }
  371.             break;
  372.         case lCloseMsg:
  373.             // do nothing
  374.             break;
  375.         default:
  376.             OTDebugStr("ListDefProc: Unrecognised message");
  377.             break;
  378.     }
  379. }
  380.  
  381. static pascal void ListUserItemUpdateProc(WindowPtr dlg, short item)
  382.     // A Dialog Manager user item update proc.  This routine
  383.     // is called by the Dialog Manager when it wants to draw
  384.     // the list user item.  We respond by drawing the list frame
  385.     // and then calling through to List Manager to draw the list itself.
  386. {
  387.     Rect itemRect;
  388.     
  389.     // This erase is critical, because List Manager is not smart
  390.     // enough to erase the area below the defined cells when
  391.     // the defined cells don't take up enough space to occupy the
  392.     // entire viewable area.
  393.     
  394.     GetDItemRect(dlg, item, &itemRect);
  395.     EraseRect(&itemRect);
  396.     
  397.     // Draw the frame.
  398.     
  399.     InsetRect(&itemRect, -1, -1);
  400.     FrameRect(&itemRect);
  401.     
  402.     // Call List Manager to draw the list.
  403.     
  404.     LUpdate(dlg->clipRgn, gLogList);
  405. }
  406.  
  407. static void RecordLogEntryToList(LogEntryPtr thisEntry)
  408.     // This routine is called at idle time when new list entries
  409.     // are noticed.  The basic idea is to append the new entries
  410.     // as rows at the end of the list.  We also have to deal with
  411.     // keeping the list size within bounds and scrolling the new
  412.     // items into view if the user asked to.
  413. {
  414.     SInt16 newRow;
  415.     SInt32 i;
  416.     LogEntryPtr anEntry;
  417.     Cell tmpCell;
  418.     
  419.     OTAssert("RecordLogEntryToList: gLogList is nil", gLogList != nil);
  420.     
  421.     // First check to see whether we have too many rows in the list.  If so,
  422.     // delete the first kNumberOfRowsToDelete rows, making sure we release
  423.     // the data associated with deleted rows.
  424.     
  425.     if ( ((**gLogList).dataBounds.bottom - (**gLogList).dataBounds.top) > kMaxListRows) {
  426.         for (i = 0; i < kNumberOfRowsToDelete; i++) {
  427.             anEntry = ListGetLogEntryForRow(i);
  428.             OTAssert("RecordLogEntryToList: No list data", anEntry != nil);
  429.             ReleaseLogEntry(anEntry);
  430.         }
  431.         LDelRow(kNumberOfRowsToDelete, 0, gLogList);
  432.     }
  433.     
  434.     // Now add the row, and set the data for the new row.
  435.     // Make sure we call RetainLogEntry to denote the extra
  436.     // reference to this data.
  437.     
  438.     LSetDrawingMode(false, gLogList);
  439.     
  440.     newRow = LAddRow(1, 32767, gLogList);
  441.     RetainLogEntry(thisEntry);
  442.     LSetDrawingMode(true, gLogList);
  443.     ListSetLogEntryForRow(newRow, thisEntry);
  444.  
  445.     tmpCell.h = 0;
  446.     tmpCell.v = newRow;
  447.  
  448.     // Finally, scroll the list to show the new item if the user
  449.     // asked us to.
  450.         
  451.     if ( gScrollWhileLogging ) {
  452.         LScroll(0, newRow, gLogList);
  453.     }
  454. }
  455.  
  456. /////////////////////////////////////////////////////////////////////
  457. #pragma mark **** General User Interface Stuff *****
  458.  
  459. static void SizeMainWindow(SInt16 newWidth, SInt16 newHeight)
  460.     // This routine is used to resize the main window,
  461.     // making sure that the list in the window is also resized
  462.     // to match.
  463. {
  464.     Rect itemRect;
  465.     
  466.     // Round the height to the closest 16 pixel boundary.
  467.     // We need to make sure the list stays a multiple of
  468.     // 16 pixels high (otherwise List Manager behaves
  469.     // suckily), and we do this by making sure the window
  470.     // is always a multiple of 16 pixels high.
  471.     
  472.     newHeight = (newHeight + 8) / 16 * 16 - 1;
  473.     
  474.     // Resize the window.
  475.     
  476.     SizeWindow(gMainWindow, newWidth, newHeight, true);
  477.     
  478.     // Now sync up the dialog item and the list view rect
  479.     // with the new window size.  This took some time to get
  480.     // right.
  481.     
  482.     GetDItemRect(gMainWindow, ditLogEntryList, &itemRect);
  483.     itemRect.right = gMainWindow->portRect.right;
  484.     itemRect.bottom = gMainWindow->portRect.bottom;
  485.     SetDItemRect(gMainWindow, ditLogEntryList, &itemRect);
  486.     itemRect.right -= 15;
  487.     itemRect.bottom -= 15;
  488.     LSize(itemRect.right - itemRect.left,
  489.             itemRect.bottom - itemRect.top,
  490.             gLogList);
  491. }
  492.  
  493. static void SetMainWindowVisibility(Boolean isVisible)
  494.     // We use this routine to show and hide the main
  495.     // window, making sure that all the UI elements that
  496.     // depend on this state are kept in sync.
  497. {
  498.     Str255 menuItemText;
  499.     
  500.     OTAssert("SetMainWindowVisibility: No list handle", gLogList != nil);
  501.  
  502.     if (isVisible) {
  503.         ShowWindow(gMainWindow);
  504.         
  505.         if ( ! TitleBarOnScreen(gMainWindow) ) {
  506.             MoveWindow(gMainWindow, 100, 100, false);
  507.         }
  508.         
  509.         GetIndString(menuItemText, rMiscStrings, strHideLogWindow);
  510.     } else {
  511.         HideWindow(gMainWindow);
  512.         GetIndString(menuItemText, rMiscStrings, strShowLogWindow);
  513.     }
  514.     SetMenuItemText( GetMenuHandle(mFile), iShowHideLogWindow, menuItemText);
  515. }
  516.  
  517. static void SetScrollWhileLogging(Boolean scrollWhileLogging)
  518.     // We use this routine to set the scroll while logging state,
  519.     // keeping the UI in sync.
  520. {
  521.     gScrollWhileLogging = scrollWhileLogging;
  522.     CheckItem( GetMenuHandle(mFile), iScrollWhileLogging, gScrollWhileLogging);
  523. }
  524.  
  525. static void DisplayError(OSStatus errNum)
  526. {
  527.     Str255 becauseString;
  528.     Str255 errNumStr;
  529.     
  530.     if (errNum != noErr && errNum != userCanceledErr) {
  531.         NumToString(errNum, errNumStr);
  532.         NewLookupErrorC(rErrorTable, errNum, becauseString);
  533.         ParamText(becauseString, errNumStr, "\p", "\p");
  534.         (void) StopAlert(rErrorAlert, gOKModalFilterUPP);
  535.     }
  536. }
  537.  
  538. static void UpdateStartStopUI(void)
  539.     // This routine is used to sync up the enabled state of
  540.     // the start and stop user interface to the current
  541.     // logging state.
  542. {
  543.     Str255 tmpStr;
  544.     
  545.     SetDControlEnable(gMainWindow, ditStop, LoggingActive());
  546.     SetDControlEnable(gMainWindow, ditStart, ! LoggingActive());
  547.     if ( LoggingActive() ) {
  548.         GetIndString(tmpStr, rMiscStrings, strStopLogging);
  549.     } else {
  550.         GetIndString(tmpStr, rMiscStrings, strStopLogging);
  551.     }
  552.     SetMenuItemText(GetMenuHandle(mFile), iStartStopLogging, tmpStr);
  553. }
  554.  
  555. static void UIStartLogging(void)
  556.     // This routine handles the user clicking on the Start
  557.     // logging button (or the equivalent menu command).  It
  558.     // gathers the logging parameters from the dialog items
  559.     // and then calls the logging back end.
  560. {
  561.     OSStatus err;
  562.     UInt32 traceInfoCount;
  563.     struct trace_ids *traceInfo;
  564.     struct trace_ids traceInfoBuffer;
  565.  
  566.     // Gather the logging parameters from the dialog.
  567.     
  568.     traceInfoCount = 0;
  569.     traceInfo = nil;
  570.     if ( GetDControlBoolean(gMainWindow, ditLogTraces) ) {
  571.         traceInfoCount = 1;
  572.         traceInfo = &traceInfoBuffer;
  573.         
  574.         if ( GetDControlBoolean(gMainWindow, ditAllTraces) ) {
  575.             traceInfoBuffer.ti_mid = -1;
  576.             traceInfoBuffer.ti_sid = -1;
  577.         } else {
  578.             traceInfoBuffer.ti_mid = gModuleID;
  579.             traceInfoBuffer.ti_sid = gStreamID;
  580.         }
  581.         if ( GetDControlValue(gMainWindow, ditTraceLevel) == iAll ) {
  582.             traceInfoBuffer.ti_level = -1;
  583.         } else {
  584.             traceInfoBuffer.ti_level = GetDControlValue(gMainWindow, ditTraceLevel) - iLevelOffset;
  585.         }
  586.     }
  587.     
  588.     // If we're supposed to be logging to a file, start the
  589.     // file logging module.
  590.     
  591.     err = noErr;
  592.     if ( GetDControlBoolean(gMainWindow, ditLogToFile) ) {
  593.         err = StartFileLogging();
  594.     }
  595.     
  596.     // Start the back end with the parameters we've already
  597.     // worked out.
  598.     
  599.     if (err == noErr) {
  600.         err = StartLogging(GetDControlBoolean(gMainWindow, ditLogErrors), traceInfoCount, traceInfo);
  601.     }
  602.     
  603.     // Update the user interface to reflect the change
  604.     // of logging state.
  605.     
  606.     if ( err == noErr ) {
  607.         UpdateStartStopUI();
  608.     }
  609.     
  610.     // Clean up.
  611.     
  612.     if (err != noErr) {
  613.         if ( FileLoggingActive() ) {
  614.             StopFileLogging();
  615.         }
  616.     }
  617.     
  618.     DisplayError(err);
  619. }
  620.  
  621. static void DoIdle(void);
  622.     // forward
  623.  
  624. static void UIStopLogging(void)
  625.     // This routine is called in response to the user
  626.     // clicking on the Stop logging button (or the equivalent
  627.     // menu command).  It basically calls through to the back
  628.     // end to stop the logging process, then flushouts out
  629.     // any remaining log entries to the log list and file,
  630.     // then shuts down file logging if it was started up.
  631. {
  632.     StopLogging();
  633.     
  634.     // Calling DoIdle will flush out any log entries that remain
  635.     // in the LIFO, which is important to do before we close the
  636.     // file.
  637.     
  638.     DoIdle();
  639.     
  640.     if ( FileLoggingActive() ) {
  641.         StopFileLogging();
  642.     }
  643.     UpdateStartStopUI();
  644. }
  645.  
  646. static Boolean UIConfirmAndStop(void)
  647.     // A generic routine which we call whenever we have to
  648.     // perform some action that will change a logging parameter.
  649.     // We don't want the current logging parameters to be out
  650.     // of sync with the user interface, and I'm too lazy to
  651.     // support adjusting the parameters dynamically, so instead
  652.     // we stop logging whenever the user changes a logging parameter.
  653.     // This routine puts up an alert asking whether that's OK,
  654.     // and then stops logging if it is.  It returns true if
  655.     // it's OK to perform a change in logging parameters (either
  656.     // because logging was off, or because we just turned it off).
  657. {
  658.     Boolean result;
  659.     
  660.     result = false;
  661.     if ( LoggingActive() ) {
  662.         if ( CautionAlert(rConfirmStopAlert, gOKCancelModalFilter) == ditOK ) {
  663.             UIStopLogging();
  664.             result = true;
  665.         }
  666.     } else {
  667.         result = true;
  668.     }
  669.     return result;
  670. }
  671.  
  672. static void UIPoseFilterDialog(void)
  673.     // This routine puts up the dialog that lets the user
  674.     // enter trace filtering parameters.  It populates the dialog
  675.     // based on gModuleID and gStreamID, and if the user clicks
  676.     // OK it writes back to those globals.
  677. {
  678.     DialogPtr dlg;
  679.     Str255 tmpStr;
  680.     SInt32 tmpLong;
  681.     SInt16 hitItem;
  682.     
  683.     // Create and prepare the dialog.
  684.     
  685.     dlg = GetNewDialog(rFilterDialog, nil, (WindowPtr) -1);
  686.     if (dlg != nil) {
  687.  
  688.         SetupDefaultButtonUserItem(dlg, ditOK, ditDefault);
  689.         
  690.         SetDControlBoolean(dlg, ditAllModules, gModuleID == -1);
  691.         SetDControlBoolean(dlg, ditChosenModules, gModuleID != -1);
  692.         if (gModuleID != -1) {
  693.             NumToString(gModuleID, tmpStr);
  694.             SetDItemText(dlg, ditModuleID, tmpStr);
  695.         }
  696.  
  697.         SetDControlBoolean(dlg, ditAllStreams, gStreamID == -1);
  698.         SetDControlBoolean(dlg, ditChosenStreams, gStreamID != -1);
  699.         if (gStreamID != -1) {
  700.             NumToString(gStreamID, tmpStr);
  701.             SetDItemText(dlg, ditStreamID, tmpStr);
  702.         }
  703.         
  704.         SelectDialogItemText(dlg, ditModuleID, 0, 32767);
  705.         
  706.         // Run the dialog and respond to user events.
  707.         
  708.         ShowWindow(dlg);
  709.         do { 
  710.             ModalDialog(gOKCancelModalFilter, &hitItem);
  711.             switch (hitItem) {
  712.                 case ditAllModules:
  713.                 case ditChosenModules:
  714.                     ToggleDControlBoolean(dlg, ditAllModules);
  715.                     ToggleDControlBoolean(dlg, ditChosenModules);
  716.                     break;
  717.                 case ditAllStreams:
  718.                 case ditChosenStreams:
  719.                     ToggleDControlBoolean(dlg, ditAllStreams);
  720.                     ToggleDControlBoolean(dlg, ditChosenStreams);
  721.                     break;
  722.                 case ditOK:
  723.                 case ditCancel:
  724.                     // do nothing
  725.                     break;
  726.                 default:
  727.                     OTDebugStr("UIPoseFilterDialog: Weird hitItem");
  728.             }
  729.         } while ( hitItem != ditOK && hitItem != ditCancel );
  730.         
  731.         // If OK, gather the results of the dialog and write
  732.         // it back to the global variables.
  733.         
  734.         if (hitItem == ditOK) {
  735.             
  736.             if ( GetDControlBoolean(dlg, ditAllModules) ) {
  737.                 gModuleID = -1;
  738.             } else {
  739.                 GetDItemText(dlg, ditModuleID, tmpStr);
  740.                 StringToNum(tmpStr, &tmpLong);
  741.                 gModuleID = tmpLong;
  742.             }
  743.  
  744.             if ( GetDControlBoolean(dlg, ditAllStreams) ) {
  745.                 gStreamID = -1;
  746.             } else {
  747.                 GetDItemText(dlg, ditStreamID, tmpStr);
  748.                 StringToNum(tmpStr, &tmpLong);
  749.                 gStreamID = tmpLong;
  750.             }
  751.             
  752.             DirtyPreferences();
  753.         }
  754.         
  755.         DisposeDialog(dlg);
  756.         
  757.     } else {
  758.         DisplayError(memFullErr);
  759.     }
  760. }
  761.  
  762. /////////////////////////////////////////////////////////////////////
  763. #pragma mark **** Log Entry Processing *****
  764.  
  765. // gLoggedCount is used to record the total number of entries
  766. // we have logged.  We use this number to maintain a static text
  767. // item in the dialog.
  768.  
  769. static UInt32 gLoggedCount = 0;
  770.  
  771. // gLastLoggedCount and gLastDroppedCount record the last value
  772. // of these two variables we used to fill out the static text fields.
  773. // We keep a record of this so that, each time around the idle loop,
  774. // we don't go to the trouble of updating the static text items unless
  775. // they have changed.
  776.  
  777. static UInt32 gLastLoggedCount = 0;
  778. static UInt32 gLastDroppedCount = 0;
  779.  
  780. static pascal void RecordLogEntry(LogEntryPtr thisEntry, void *refCon)
  781.     // This routine is a callback, called by the logging module (which
  782.     // we in turn called at idle time), when a new log entry has been
  783.     // found.  We call our two logging subsystems (on screen list
  784.     // and file) to log the entry to the appropriate places.  The
  785.     // routines we call are responsible for retaining thisEntry
  786.     // if they need it to hang around; otherwise it will be disposed
  787.     // when this routine returns.
  788. {
  789.     #pragma unused(refCon)
  790.     
  791.     RecordLogEntryToList(thisEntry);
  792.     RecordLogEntryToFile(thisEntry);
  793.     
  794.     gLoggedCount += 1;
  795. }
  796.  
  797. /////////////////////////////////////////////////////////////////////
  798. #pragma mark **** Menu Handling *****
  799.  
  800. static void AdjustMenus(void)
  801.     // This routine adjusts the menus based on the state of the
  802.     // program in preparation for a call to MenuKey or MenuSelect.
  803. {
  804.     MenuHandle menuH;
  805.     Boolean logAtFront;
  806.     Boolean logHasSelection;
  807.     
  808.     // File
  809.     
  810.     menuH = GetMenuHandle(mFile);
  811.     SetMenuItemEnable(menuH, iClose, FrontWindow() == gMainWindow );
  812.     
  813.     // Edit
  814.     
  815.     menuH = GetMenuHandle(mEdit);
  816.     logAtFront = (FrontWindow() == gMainWindow);
  817.     logHasSelection = (LSelectedLine(gLogList) != -1);
  818.     DisableItem(menuH, iUndo);
  819.     SetMenuItemEnable(menuH, iCut, logAtFront && logHasSelection);
  820.     SetMenuItemEnable(menuH, iCopy, logAtFront && logHasSelection);
  821.     DisableItem(menuH, iPaste);
  822.     SetMenuItemEnable(menuH, iClear, logAtFront && logHasSelection);
  823.     SetMenuItemEnable(menuH, iSelectAll, logAtFront && ! LIsEmpty(gLogList) );
  824. }
  825.  
  826. static void CopySelectedListEntriesToClip(void)
  827.     // This routine is used to implement the Copy and Cut
  828.     // menu commands.  It iterates through the list of selected
  829.     // cells and adds the text from each of them into a handle.
  830.     // It then puts that handle into the system scrap.
  831. {
  832.     OSStatus err;
  833.     Cell listCell;
  834.     Handle clipTextH;
  835.     LogEntryPtr anEntry;
  836.     char *entryAsText;
  837.  
  838.     // Create a handle for the text we're copying.
  839.     
  840.     clipTextH = NewHandle(0);
  841.     err = CheckMemError(clipTextH);
  842.     if (err == noErr) {
  843.     
  844.         // Iterate through the selected cells, adding their
  845.         // text representations to the handle.
  846.         
  847.         listCell.v = 0;
  848.         listCell.h = 0;
  849.         while ( LGetSelect(true, &listCell, gLogList) ) {
  850.  
  851.             anEntry = ListGetLogEntryForRow(listCell.v);
  852.             OTAssert("ClearSelectedListEntries: No list data", anEntry != nil);
  853.  
  854.             entryAsText = LogEntryToCString(anEntry);
  855.             err = PtrAndHand(entryAsText, clipTextH, OTStrLength(entryAsText));
  856.             if (err != noErr) {
  857.                 break;
  858.             }
  859.             listCell.v += + 1;
  860.             listCell.h = 0;
  861.         }
  862.     }
  863.     
  864.     // Put the newly created text into the system scrap.
  865.     
  866.     if (err == noErr) {
  867.         (void) ZeroScrap();
  868.         err = PutScrap( GetHandleSize(clipTextH), 'TEXT', *clipTextH );
  869.         if (err > 0) {
  870.             err = 0;
  871.         }
  872.     }
  873.     
  874.     // Clean up.
  875.     
  876.     if ( clipTextH != nil ) {
  877.         DisposeHandle(clipTextH);
  878.     }
  879.     DisplayError(err);
  880. }
  881.  
  882. static void ClearSelectedListEntries(void)
  883.     // This routine is used to implement the Cut and Clear
  884.     // menu commands.  It iterates through the list of selected
  885.     // cells deleting them.
  886. {
  887.     Cell listCell;
  888.     LogEntryPtr anEntry;
  889.     
  890.     // I turn off drawing mode, delete all the selected cells, turn
  891.     // drawing mode back on and then invalidate the entire list.  This
  892.     // causes flashing if there's only one cell selected, but it works
  893.     // much better in the case where there are *lots* of cells selected.
  894.     // I chose to do it this way because I think the latter case will be
  895.     // more prevelant, and I don't have time to handle both cases properly.
  896.  
  897.     LSetDrawingMode(false, gLogList);
  898.     listCell.v = 0;
  899.     listCell.h = 0;
  900.     while ( LGetSelect(true, &listCell, gLogList) ) {
  901.  
  902.         anEntry = ListGetLogEntryForRow(listCell.v);
  903.         OTAssert("ClearSelectedListEntries: No list data", anEntry != nil);
  904.         ReleaseLogEntry(anEntry);
  905.         LDelRow(1, listCell.v, gLogList);
  906.  
  907.         // Don't increment listCell.v because we've just deleted it.
  908.         // LGetSelect will continue looking from the current co-ordinates,
  909.         // which will be the next selected cell.
  910.         // listCell.v += + 1;
  911.         listCell.h = 0;
  912.     }
  913.     LSetDrawingMode(true, gLogList);
  914.     InvalDItem(gMainWindow, ditLogEntryList);
  915. }
  916.  
  917. static void DoMenu(SInt32 menuAndItem)
  918.     // Handle a menu command.  menuAndItme is a longint
  919.     // with the menu number in the top 16 bits and the
  920.     // menu item in the bottom 16, ie the format returned
  921.     // by MenuSelect and MenuKey.
  922.     //
  923.     // Yeah, this should be more than one procedure (-:
  924. {
  925.     SInt16 menu;
  926.     SInt16 item;
  927.     UInt32 startTicks;
  928.     UInt32 junkLong;
  929.     Str255 daName;
  930.     SInt16 junk;
  931.     VersRecHndl versH;
  932.     SInt8 s;
  933.     
  934.     startTicks = TickCount();
  935.     
  936.     menu = menuAndItem >> 16;
  937.     item = menuAndItem & 0x0FFFF;
  938.     switch (menu) {
  939.         case mApple:
  940.             switch (item) {
  941.                 case iAbout:
  942.                     versH = (VersRecHndl) Get1Resource('vers', 1);
  943.                     OTAssert("", versH != nil);
  944.                     s = HGetState( (Handle) versH );
  945.                     HLock( (Handle) versH );
  946.                     ParamText( (**versH).shortVersion, "\p", "\p", "\p");
  947.                     junk = Alert(rAboutBox, gOKModalFilterUPP);
  948.                     HSetState( (Handle) versH, s );
  949.                     break;
  950.                 default:
  951.                     GetMenuItemText(GetMenuHandle(mApple), item, daName);
  952.                     junk = OpenDeskAcc(daName);
  953.                     break;
  954.             }
  955.             break;
  956.         case mFile:
  957.             switch (item) {
  958.                 case iClose:
  959.                     SetMainWindowVisibility(false);
  960.                     DirtyPreferences();
  961.                     break;
  962.                 case iShowHideLogWindow:
  963.                     SetMainWindowVisibility( ! ((WindowPeek) gMainWindow)->visible );
  964.                     DirtyPreferences();
  965.                     break;
  966.                 case iStartStopLogging:
  967.                     if ( LoggingActive() ) {
  968.                         FlashDItem(gMainWindow, ditStop);
  969.                         UIStopLogging();
  970.                     } else {
  971.                         FlashDItem(gMainWindow, ditStart);
  972.                         UIStartLogging();
  973.                     }
  974.                     break;
  975.                 case iScrollWhileLogging:
  976.                     SetScrollWhileLogging( ! gScrollWhileLogging);
  977.                     DirtyPreferences();
  978.                     break;
  979.                 case iQuit:
  980.                     gQuitNow = true;
  981.                     break;
  982.                 default:
  983.                     OTDebugStr("DoMenu: Weird File menu item");
  984.                     break;
  985.             }
  986.             break;
  987.         case mEdit:
  988.             OTAssert("DoMenu: Edit menu should be disabled if gMainWindow not at front", gMainWindow == FrontWindow());
  989.             switch (item) {
  990.                 case iCut:
  991.                     CopySelectedListEntriesToClip();
  992.                     ClearSelectedListEntries();
  993.                     break;
  994.                 case iCopy:
  995.                     CopySelectedListEntriesToClip();
  996.                     break;
  997.                 case iClear:
  998.                     ClearSelectedListEntries();
  999.                     break;
  1000.                 case iSelectAll:
  1001.                     LSelectAll(gLogList);
  1002.                     break;
  1003.                 default:
  1004.                     OTDebugStr("DoMenu: Weird Edit menu item");
  1005.                     break;
  1006.             }
  1007.         default:
  1008.             // OTDebugStr("DoMenu: Weird menu item");
  1009.             break;
  1010.     }
  1011.     
  1012.     // Unhighlight the menu, delaying to avoid a quick flash.
  1013.     
  1014.     if ( ! gQuitNow ) {
  1015.         while ( TickCount() < startTicks + 6 ) {
  1016.             Delay(1, &junkLong);
  1017.         }
  1018.         HiliteMenu(0);
  1019.     }
  1020. }
  1021.  
  1022. /////////////////////////////////////////////////////////////////////
  1023. #pragma mark **** Low Level Event Handling *****
  1024.  
  1025. static void DoMouseDown(EventRecord *event)
  1026.     // Handle a mouse down event.  This is standard
  1027.     // Mac OS stuff.  Call FindWindow to determine what
  1028.     // type and event it was (and what window it happened in)
  1029.     // and then handle each case.
  1030. {
  1031.     SInt16 partCode;
  1032.     WindowPtr hitWindow;
  1033.     long growResult;
  1034.     
  1035.     partCode = FindWindow(event->where, &hitWindow);
  1036.     switch ( partCode ) {
  1037.         case inGoAway:
  1038.             if ( TrackGoAway(hitWindow, event->where) ) {
  1039.                 if ( hitWindow == gMainWindow ) {
  1040.                     SetMainWindowVisibility(false);
  1041.                     DirtyPreferences();
  1042.                 } else {
  1043.                     OTDebugStr("DoMouseDown: Does not handle closing other windows");
  1044.                 }
  1045.             }
  1046.             break;
  1047.         case inMenuBar:
  1048.             AdjustMenus();
  1049.             DoMenu(MenuSelect(event->where));
  1050.             break;
  1051.         case inDrag:
  1052.             DragWindow(hitWindow, event->where, &qd.screenBits.bounds);
  1053.             DirtyPreferences();
  1054.             break;
  1055.         case inGrow:
  1056.             growResult = GrowWindow(hitWindow, event->where, &gMainWindowGrowBounds);
  1057.             if ( growResult != 0 ) {
  1058.                 Rect tmpRect;
  1059.                 
  1060.                 if ( hitWindow == gMainWindow ) {
  1061.                     SizeMainWindow(growResult, growResult >> 16);
  1062.                     DirtyPreferences();
  1063.                 } else {
  1064.                     SizeWindow(hitWindow, growResult, growResult >> 16, true);
  1065.                 }
  1066.                 
  1067.                 tmpRect = hitWindow->portRect;
  1068.                 tmpRect.top = tmpRect.bottom - 15;
  1069.                 InvalRect(&tmpRect);
  1070.                 tmpRect = hitWindow->portRect;
  1071.                 tmpRect.left = tmpRect.right - 15;
  1072.                 InvalRect(&tmpRect);
  1073.             }
  1074.             break;
  1075.         case inZoomIn:
  1076.         case inZoomOut:
  1077.             if ( TrackBox(hitWindow, event->where, partCode) ) {
  1078.                 ZoomWindow(hitWindow, partCode, false);
  1079.                 if (hitWindow == gMainWindow) {
  1080.                     SizeMainWindow(hitWindow->portRect.right - hitWindow->portRect.left,
  1081.                                     hitWindow->portRect.bottom - hitWindow->portRect.top);
  1082.                 }
  1083.                 
  1084.                 // Force everything to redraw.  I know this isn't perfect,
  1085.                 // but I'd rather have window zooming working but flashy
  1086.                 // than not have it.
  1087.                 
  1088.                 EraseRect(&hitWindow->portRect);
  1089.                 InvalRect(&hitWindow->portRect);
  1090.             }
  1091.             break;
  1092.         default:
  1093.             // do nothing
  1094.             break;
  1095.     }
  1096. }
  1097.  
  1098. static void DoIdle(void)
  1099.     // Called each time through the main event loop.  This routine
  1100.     // handles getting the new log entries out of the log module
  1101.     // and into RecordLogEntry, which sends them to the file and list
  1102.     // logging code.  It also idles the file and preferences modules,
  1103.     // which delay actions for a time to prevent unnecessary disk thrashing.
  1104. {
  1105.     Str255 tmpStr;
  1106.     UInt32 droppedCount;
  1107.     
  1108.     // Don't ask me why, or I'll start to whimper.
  1109.  
  1110.     OTIdle();
  1111.  
  1112.     // OK, I'll explain.  Basically there's a weird concurrency
  1113.     // hole in OT such that, if you don't have any other network
  1114.     // activity, strlog messages will never get delivered
  1115.     // to the client.  I've reported this as a bug [Radar ID â€¢â€¢â€¢],
  1116.     /// but it's easy to work around (calling OTIdle will deliver
  1117.     // the messages) and it's so deep in the OT kernel that 
  1118.     // I'm not sure anyone will ever want to take the risk
  1119.     // associated with fixing it.
  1120.     
  1121.     ForEachNewLogEntry(RecordLogEntry, nil);
  1122.     
  1123.     FileLoggingIdle();
  1124.  
  1125.     PreferencesIdle();
  1126.  
  1127.     // Update the static text to reflect the number
  1128.     // of logs we've seen and/or dropped.
  1129.     
  1130.     if ( gLoggedCount != gLastLoggedCount ) {
  1131.         NumToString(gLoggedCount, tmpStr);
  1132.         SetDItemText(gMainWindow, ditLoggedCount, tmpStr);
  1133.         gLastLoggedCount = gLoggedCount;
  1134.     }
  1135.     
  1136.     droppedCount = NumberOfDroppedLogEntries();
  1137.     if ( droppedCount != gLastDroppedCount ) {
  1138.         NumToString(droppedCount, tmpStr);
  1139.         SetDItemText(gMainWindow, ditDroppedCount, tmpStr);
  1140.         gLastDroppedCount = droppedCount;
  1141.     }
  1142. }
  1143.  
  1144. static pascal void GetLogListCellText(ListHandle listH, Cell listCell, Str255 cellText)
  1145.     // This routine is a callback from LDoKey to get the text of
  1146.     // a cell.  We return the sequence number for the log entry.
  1147.     // This allows LDoKey to implement "select by typing".
  1148. {
  1149.     #pragma unused(listH)
  1150.     LogEntryPtr thisEntry;
  1151.     
  1152.     cellText[0] = 0;
  1153.     
  1154.     thisEntry = ListGetLogEntryForRow(listCell.v);
  1155.     OTAssert("GetLogListCellText: No data for cell", thisEntry != nil);
  1156.     if (thisEntry != nil) {
  1157.         NumToString(thisEntry->fLogHeader.seq_no, cellText);
  1158.     }
  1159. }
  1160.  
  1161. static void DoUpdateEvent(EventRecord *event)
  1162.     // In general we handle update events through
  1163.     // the Dialog Manager DialogSelect routine.  However,
  1164.     // for the main window, we have to manually draw the
  1165.     // grow box.  This is not as easy as it might seem, because
  1166.     // we don't want the silly fake scroll bars in our window.
  1167.     // We we set the clip region to the bottom right 15 x 15
  1168.     // rectangle, so we get the grow box and nothing else.
  1169.     //
  1170.     // Of course, Mac OS 8 Appearance does silly things here
  1171.     // too, because the grow box is now part of the window
  1172.     // structure region.  But it recognises the call to DrawGrowIcon
  1173.     // and does The Right Thing (tm).
  1174. {
  1175.     RgnHandle tmpRgn;
  1176.     Rect growRect;
  1177.  
  1178.     if ( (WindowPtr) event->message == gMainWindow ) {
  1179.         tmpRgn = NewRgn();
  1180.         OTAssert("MainEventLoop: Could not create temporary region", tmpRgn != nil);
  1181.         
  1182.         CopyRgn(gMainWindow->clipRgn, tmpRgn);
  1183.         growRect = gMainWindow->portRect;
  1184.         growRect.top = growRect.bottom - 15;
  1185.         growRect.left = growRect.right - 15;
  1186.         ClipRect(&growRect);
  1187.         
  1188.         DrawGrowIcon( (WindowPtr) event->message );
  1189.         
  1190.         SetClip(tmpRgn);
  1191.         DisposeRgn(tmpRgn);
  1192.     }
  1193. }
  1194.  
  1195. static void DoMainWindowHit(EventRecord *event, SInt16 hitItem, SInt16 oldTraceLevel)
  1196.     // Handle a hit on an item in the main dialog window.
  1197.     // A big switch state with a bunch of UI twiddling
  1198.     // that, if we had a real windowing toolbox, would not
  1199.     // be necessary!
  1200.     //
  1201.     // oldTraceLevel is the old value of the trace level popup
  1202.     // menu.  The MainEventLoop saves this value before calling
  1203.     // DialogSelect.  DialogSelect will change the popup and
  1204.     // tells us that it's done so.  We then ask the user to
  1205.     // confirm whether they want to stop logging.  If they say
  1206.     // they don't, we reset the popup back to the old value.
  1207. {
  1208.     Point localWhere;
  1209.     
  1210.     switch ( hitItem ) {
  1211.         case ditStart:
  1212.             UIStartLogging();
  1213.             break;
  1214.         case ditStop:
  1215.             UIStopLogging();
  1216.             break;
  1217.  
  1218.         case ditLogErrors:
  1219.         case ditLogTraces:
  1220.         case ditLogToFile:
  1221.             if ( UIConfirmAndStop() ) {
  1222.                 ToggleDControlBoolean(gMainWindow, hitItem);
  1223.                 DirtyPreferences();
  1224.             }
  1225.             break;
  1226.  
  1227.         case ditAllTraces:
  1228.         case ditTracesMatching:
  1229.             if ( UIConfirmAndStop() ) {
  1230.                 SetDControlBoolean(gMainWindow, ditAllTraces, hitItem == ditAllTraces);
  1231.                 SetDControlBoolean(gMainWindow, ditTracesMatching, hitItem == ditTracesMatching);
  1232.                 SetDControlEnable(gMainWindow, ditSetupFilter, hitItem == ditTracesMatching);
  1233.                 DirtyPreferences();
  1234.             }
  1235.             break;
  1236.  
  1237.         case ditSetupFilter:
  1238.             if ( UIConfirmAndStop() ) {
  1239.                 UIPoseFilterDialog();
  1240.             }
  1241.             break;
  1242.             
  1243.         case ditTraceLevel:
  1244.             if ( UIConfirmAndStop() ) {
  1245.                 DirtyPreferences();
  1246.             } else {
  1247.                 SetDControlValue(gMainWindow, ditTraceLevel, oldTraceLevel);
  1248.             }
  1249.             break;
  1250.             
  1251.         case ditLogEntryList:
  1252.             localWhere = event->where;
  1253.             GlobalToLocal(&localWhere);
  1254.             (void) LClick(localWhere, event->modifiers, gLogList);
  1255.             break;
  1256.         default:
  1257.             OTDebugStr("DoMainWindowHit: Unexpected hitItem");
  1258.             break;
  1259.     }
  1260. }
  1261.  
  1262. static void MainEventLoop(void)
  1263. {
  1264.     EventRecord event;
  1265.     WindowRef hitWindow;
  1266.     SInt16 hitItem;
  1267.     SInt16 oldTraceLevel;
  1268.     
  1269.     gQuitNow = false;
  1270.     do {
  1271.         DoIdle();
  1272.         
  1273.         (void) WaitNextEvent(everyEvent, &event, 60, nil);
  1274.  
  1275.         SetPort(gMainWindow);
  1276.         
  1277.         if ( IsDialogEvent(&event) ) {
  1278.             oldTraceLevel = GetDControlValue(gMainWindow, ditTraceLevel);
  1279.             if ( DialogSelect(&event, &hitWindow, &hitItem) ) {
  1280.                 if (hitWindow == gMainWindow) {
  1281.                     DoMainWindowHit(&event, hitItem, oldTraceLevel);
  1282.                 } else {
  1283.                     OTDebugStr("MainEventLoop: What other dialogs?");
  1284.                 }
  1285.             }
  1286.         }
  1287.         switch (event.what) {
  1288.             case keyDown:
  1289.             case autoKey:
  1290.                 if ( event.what == keyDown && (event.modifiers & cmdKey) != 0 ) {
  1291.                     AdjustMenus();
  1292.                     DoMenu(MenuKey(event.message & charCodeMask));
  1293.                 } else if ( FrontWindow() == gMainWindow) {
  1294.                     UInt8 typedChar;
  1295.                     
  1296.                     typedChar = (event.message & charCodeMask);
  1297.                     if ( typedChar == kClearChar ||
  1298.                             typedChar == kBackSpaceChar ||
  1299.                             typedChar == kDelChar ) {
  1300.                         ClearSelectedListEntries();
  1301.                     } else {
  1302.                         LDoKey(gLogList, &event, GetLogListCellText);
  1303.                     }
  1304.                 }
  1305.                 break;
  1306.             case mouseDown:
  1307.                 DoMouseDown(&event);
  1308.                 break;
  1309.             case updateEvt:
  1310.                 DoUpdateEvent(&event);
  1311.                 break;
  1312.             case activateEvt:
  1313.                 if ( (WindowPtr) event.message == gMainWindow ) {
  1314.                     LActivate( (event.modifiers & 1) != 0, gLogList);
  1315.                 }
  1316.                 break;
  1317.             case kHighLevelEvent:
  1318.                 (void) AEProcessAppleEvent(&event);
  1319.                 break;
  1320.             default:
  1321.                 // do nothing
  1322.                 break;
  1323.         }
  1324.     } while ( ! gQuitNow );
  1325.  
  1326.     // As we're quitting, we'd better shut down the logging
  1327.     // systems.
  1328.         
  1329.     if ( LoggingActive() ) {
  1330.         FlashDItem(gMainWindow, ditStop);
  1331.         UIStopLogging();
  1332.     }
  1333. }
  1334.  
  1335. /////////////////////////////////////////////////////////////////////
  1336. #pragma mark **** Prefs Management *****
  1337.  
  1338. enum {
  1339.     kPrefsMagic = kCreator
  1340. };
  1341.  
  1342. // PrefsRecord is stored in Internet Config (if it is
  1343. // installed) because it's a handy preferences system
  1344. // and I really don't want to create yet another file
  1345. // in the Preferences folder.  Because it's stored
  1346. // on the disk, it has to be 68K aligned (becasue it might
  1347. // be written by a 68K and read by a PPC, or vice versa).
  1348. //
  1349. // The fields in the record pretty much mirror the state
  1350. // in the user interface elements.  We save all this state
  1351. // so the user (namely me during testing!) doesn't have to
  1352. // reset it each time they launch the program.
  1353.  
  1354. #pragma options align=mac68k
  1355. struct PrefsRecord {
  1356.     OSType    magic;                // kPrefsMagic
  1357.     Rect    mainWindowPosition;
  1358.     SInt16    moduleID;
  1359.     SInt16    streamID;
  1360.     Boolean    logToFile;
  1361.     Boolean    logErrors;
  1362.     Boolean    logTraces;
  1363.     Boolean    tracesMatching;
  1364.     Boolean mainWindowVisible;
  1365.     Boolean scrollWhileLogging;
  1366. };
  1367. typedef struct PrefsRecord PrefsRecord, *PrefsRecordPtr;
  1368. #pragma options align=reset
  1369.  
  1370. static UInt8 *gICPrefsKey = "\p536C5677•PrefsRecord";
  1371.     // The Internet Config key we use to save the PrefsRecord.
  1372.  
  1373. static ICInstance gICInstance = nil;
  1374.     // Our connection to Internet Config.  Note that
  1375.     // gICInstance != nil implies that it's safe to call IC.
  1376.  
  1377. enum {
  1378.     kPrefChangeWriteDelay = 600,
  1379.         // The length of time between when the preferences
  1380.         // are dirtied and when we write them.  This allows
  1381.         // multiple preference writes to be collated into one,
  1382.         // thereby saving some disk thrashing.
  1383.     
  1384.     kPrefsCleanTicks = (UInt32) -(kPrefChangeWriteDelay+1)
  1385.         // When the preferences are clean we set gTicksOfLastPrefChange
  1386.         // to this value to prevent further writes from happening.
  1387.         // This corresponds to "far in the future".
  1388. };
  1389.  
  1390. static UInt32 gTicksOfLastPrefChange = kPrefsCleanTicks;
  1391.     // The TickCount time at which we last modified the
  1392.     // preferences.  If TickCount is more than this value plus
  1393.     // kPrefChangeWriteDelay, we need to save the preferences now.
  1394.  
  1395. static void RestorePreferences(void)
  1396.     // Restore the preferences from Internet Config,
  1397.     // and put all the saved values back into the appropriate
  1398.     // state holder in the program.
  1399. {
  1400.     OSStatus err;
  1401.     PrefsRecord prefs;
  1402.     SInt32 prefsSize;
  1403.     ICAttr junkAttr;
  1404.     
  1405.     OTAssert("RestorePreferences: Need a main window to do this", gMainWindow != nil);
  1406.     OTAssert("RestorePreferences: Need a log list to do this", gLogList != nil);
  1407.  
  1408.     // Start by creating a connection to IC, only if it's present
  1409.     // (and we linked to it).
  1410.         
  1411.     err = noErr;
  1412.     #if GENERATINGCFM
  1413.         if ( (void *) ICStart == (void *) kUnresolvedCFragSymbolAddress ) {
  1414.             err = -1;
  1415.         }
  1416.     #endif
  1417.     if (err == noErr) {
  1418.         err = ICStart(&gICInstance, kCreator);
  1419.     }
  1420.     if (err == noErr) {
  1421.         err = ICFindConfigFile(gICInstance, 0, nil);
  1422.     }
  1423.     
  1424.     // Then read the preferencs from IC.  Ignore truncated errors.
  1425.     // It probably means that the preferences were last written by 
  1426.     // a more modern version of this program, but it should damn well
  1427.     // make sure that our fields are the first fields of its preferences
  1428.     // record.
  1429.     
  1430.     if (err == noErr) {
  1431.         prefsSize = sizeof(PrefsRecord);
  1432.         err = ICGetPref(gICInstance, gICPrefsKey, &junkAttr, (void *) &prefs, &prefsSize);
  1433.         // err = -666;
  1434.         if (err == icTruncatedErr) {
  1435.             prefsSize = sizeof(PrefsRecord);
  1436.             err = noErr;
  1437.         }
  1438.     }
  1439.     
  1440.     // Basical sanity checks.
  1441.     
  1442.     if (err == noErr && prefsSize < sizeof(PrefsRecord)) {
  1443.         err = -2;
  1444.     }
  1445.     if (err == noErr && prefs.magic != kPrefsMagic) {
  1446.         err = -3;
  1447.     }
  1448.  
  1449.     // If we could not read preferences, use defaults instead.
  1450.         
  1451.     if (err != noErr) {
  1452.         prefs.magic = kPrefsMagic;
  1453.         GetWindowRect(gMainWindow, &prefs.mainWindowPosition);
  1454.         prefs.moduleID = 0;
  1455.         prefs.streamID = 0;
  1456.         prefs.logToFile = false;
  1457.         prefs.logErrors = true;
  1458.         prefs.logTraces = true;
  1459.         prefs.tracesMatching = false;
  1460.         prefs.mainWindowVisible = true;
  1461.         prefs.scrollWhileLogging = true;
  1462.     }
  1463.     
  1464.     // Now set up the program state from the preferences.
  1465.     
  1466.     gModuleID = prefs.moduleID;
  1467.     gStreamID = prefs.streamID;
  1468.     
  1469.     SetDControlBoolean(gMainWindow, ditLogToFile, prefs.logToFile);
  1470.     SetDControlBoolean(gMainWindow, ditLogErrors, prefs.logErrors);
  1471.     SetDControlBoolean(gMainWindow, ditLogTraces, prefs.logTraces);
  1472.     SetDControlBoolean(gMainWindow, ditTracesMatching, prefs.tracesMatching);
  1473.     SetDControlBoolean(gMainWindow, ditAllTraces, ! prefs.tracesMatching);
  1474.  
  1475.     SetDControlEnable(gMainWindow, ditSetupFilter, prefs.tracesMatching);
  1476.     
  1477.     MoveWindow(gMainWindow, prefs.mainWindowPosition.left, prefs.mainWindowPosition.top, false);
  1478.     SizeMainWindow(prefs.mainWindowPosition.right - prefs.mainWindowPosition.left,
  1479.                     prefs.mainWindowPosition.bottom - prefs.mainWindowPosition.top);
  1480.     SetMainWindowVisibility(prefs.mainWindowVisible);
  1481.     SetScrollWhileLogging(prefs.scrollWhileLogging);
  1482. }
  1483.  
  1484. static void SavePreferences(void)
  1485.     // Save the preferences to Internet Config.
  1486. {
  1487.     OSStatus junk;
  1488.     PrefsRecord prefs;
  1489.  
  1490.     // Don't even think about this if we don't have a connection
  1491.     // to IC.
  1492.         
  1493.     if ( gICInstance != nil ) {
  1494.     
  1495.         // Fill out the PrefsRecord from the program state
  1496.         // and write it out.
  1497.         
  1498.         prefs.magic = kPrefsMagic;
  1499.         GetWindowRect(gMainWindow, &prefs.mainWindowPosition);
  1500.         prefs.moduleID = gModuleID;
  1501.         prefs.streamID = gStreamID;
  1502.         prefs.logToFile = GetDControlBoolean(gMainWindow, ditLogToFile);
  1503.         prefs.logErrors = GetDControlBoolean(gMainWindow, ditLogErrors);
  1504.         prefs.logTraces = GetDControlBoolean(gMainWindow, ditLogTraces);
  1505.         prefs.tracesMatching = GetDControlBoolean(gMainWindow, ditTracesMatching);
  1506.         prefs.mainWindowVisible =  ((WindowPeek) gMainWindow)->visible;
  1507.         prefs.scrollWhileLogging = gScrollWhileLogging;
  1508.         
  1509.         junk = ICSetPref(gICInstance, gICPrefsKey, ICattr_no_change, (void *) &prefs, sizeof(PrefsRecord));
  1510.         OTAssert("SavePreferences: Error writing prefs", junk == noErr);
  1511.     }
  1512.     gTicksOfLastPrefChange = kPrefsCleanTicks;
  1513. }
  1514.  
  1515. static void DirtyPreferences(void)
  1516.     // Called by the general code when something that is
  1517.     // stored in the prefs is modified.  We note the time
  1518.     // this happens, and the idle function should eventually
  1519.     // notice this and call SavePreferences.
  1520. {
  1521.     gTicksOfLastPrefChange = TickCount();
  1522. }
  1523.  
  1524. static void PreferencesIdle(void)
  1525.     // Our idle function, called by DoIdle, once each time around
  1526.     // the event loop.  We see how long the preferences have been dirty
  1527.     // and write them if it's been too long.
  1528. {
  1529.     if ( TickCount() > gTicksOfLastPrefChange + kPrefChangeWriteDelay) {
  1530.         SavePreferences();
  1531.     }
  1532. }
  1533.  
  1534. static void TermPreferences(void)
  1535.     // This routine is called when the application quits
  1536.     // to save any preferences that haven't yet been saved by
  1537.     // our idle function and shut down our connection to IC.
  1538. {
  1539.     OSStatus junk;
  1540.     
  1541.     if ( gICInstance != nil ) {
  1542.     
  1543.         if ( gTicksOfLastPrefChange != kPrefsCleanTicks ) {
  1544.             SavePreferences();
  1545.         }
  1546.     
  1547.         junk = ICStop(gICInstance);
  1548.         OTAssert("TermPreferences: Error closing IC", junk == noErr);
  1549.         gICInstance = nil;
  1550.     }
  1551. }
  1552.  
  1553. /////////////////////////////////////////////////////////////////////
  1554. #pragma mark **** Startup and Shutdown Code *****
  1555.  
  1556. static void InitMacToolbox(void)
  1557.     // Init all standard Mac OS managers.
  1558.     // How many times have I written this?
  1559. {
  1560.     InitGraf(&qd.thePort);
  1561.     InitFonts();
  1562.     InitWindows();
  1563.     InitMenus();
  1564.     TEInit();
  1565.     InitDialogs(nil);
  1566.     MaxApplZone();
  1567.     InitCursor();
  1568. }
  1569.  
  1570. // InitOpenTransportWithMemoryLimit Big Picture
  1571. // --------------------------------------------
  1572. //
  1573. // The LogEngine uses the OT memory allocation routines
  1574. // (OTAllocMem, OTFreeMem) to allocate space for log entries in
  1575. // the notifier.  This memory comes from an ASLM memory pool
  1576. // that OT creates for us when we call InitOpenTransport.  However,
  1577. // this pool has some bad characteristics:
  1578. //
  1579. //     1.    The pool starts off very small, and only grows when we allocate
  1580. //         memory from it.  As we do all our allocation from our notifier
  1581. //        (which is interrupt time with respect to the system Memory Manager)
  1582. //        the pool can't grow immediately.  So the pool will often run
  1583. //        be full (ie OTAllocMem will return nil) even though the application
  1584. //        has plenty of memory.  The pool will later grow, but we've already
  1585. //        dropped the log entry on the floor.
  1586. //
  1587. //    2.    Because the pool starts off small and grows by pieces, we get
  1588. //        an extremely fragmented pool.  While this works, its definitely
  1589. //        sub-optimal.
  1590. //
  1591. //    3.    If we're being hammered by strlog (ie people are calling strlog a
  1592. //        lot), the pool will keep growing and there's nothing to stop
  1593. //        the pool consuming our entire application heap.  When it does so,
  1594. //        various toolbox routines (eg QuickDraw) fail ungracefully, ie
  1595. //        SysError(25).
  1596. //
  1597. // There are a number of steps in my solution to this.  The first step
  1598. // is to call InitLibraryManager myself.  This allows me to specify
  1599. // the size of the pool. InitOpenTransport notices that I have inited ASLM
  1600. // myself and doesn't do it itself.  Thus OTAllocMem gets its memory from
  1601. // whatever pool ASLM created.  This gets around problems 1 and 2.
  1602. //
  1603. // The second step is to create a subsidiary zone within my application heap
  1604. // and specify that ASLM should create its pool in that zone (by supplying
  1605. // kCurrentZone to InitLibraryManager).  Thus the pool can grow up to the
  1606. // point where the memory in the subsidiary zone is exhausted.  At that point,
  1607. // the pool will no longer grow.  So the pool will not steal memory from
  1608. // the main application heap.  This gets around problem 3.
  1609. //
  1610. // There are a number of other ways I could have achieved the same results.
  1611. // The ASLM memory manager is very flexible.  For example, I could have removed
  1612. // OT's TPoolNotifier from the pool, which would prevent the pool from growing.
  1613. // However, this solution does not require me to use any ASLM C++ stuff,
  1614. // which makes the code more compiler independent.
  1615.  
  1616. enum {
  1617.     kBytesReservedForToolboxInApplicationZone = 100L * 1024L,
  1618.         // This value represents the minimum number of contiguous
  1619.         // bytes that should remain in the application heap after
  1620.         // we've created the subsidiary zone.
  1621.         
  1622.     kBytesReservedForASLMInSubsidaryzone = 2048,
  1623.         // This value represents the number of bytes in the subsidiary
  1624.         // zone we should leave lying around for general purpose ASLM
  1625.         // use.  The remaining bytes in the subsidiary zone are
  1626.         // dedicated to the ASLM memory pool, ie are passed as the pool
  1627.         // size to InitLibraryManager.
  1628.         
  1629.     kMinimumBytesForUsInSubsidiaryZone = 10 * 1024
  1630.         // This value represents the minimum pool size we pass to
  1631.         // InitLibraryManager.  If we can't create a pool of at least
  1632.         // this size, the application doesn't start up.
  1633. };
  1634.  
  1635. static OSStatus InitOpenTransportWithMemoryLimit(void)
  1636.     // See above for an explanation of the big picture here.
  1637. {
  1638.     OSStatus err;
  1639.     SInt32 junkTotalFree;
  1640.     SInt32 contigFree;
  1641.     SInt32 zoneSize;
  1642.     Ptr gSubsidiaryZone;
  1643.     THz oldZone;
  1644.  
  1645.     // Debugger();
  1646.     
  1647.     // First call the system Memory Manager to determine the largest
  1648.     // contiguous block in the heap.
  1649.     
  1650.     PurgeSpace(&junkTotalFree, &contigFree);
  1651.     
  1652.     // If it's too small for our toolbox needs, bail out.
  1653.     
  1654.     err = noErr;
  1655.     if (contigFree < kBytesReservedForToolboxInApplicationZone) {
  1656.         err = memFullErr;
  1657.     }
  1658.     
  1659.     // Now calculate the size of the zone we're going to create.
  1660.     // It's the size of the largest contiguous block, minus
  1661.     // the size of we reserve for toolbox needs, rounded to the nearest KB.
  1662.     // If the zone size isn't big enough enough to hold our minimum
  1663.     // pool size and the amount we reserve for ASLM, bail out.
  1664.     
  1665.     if (err == noErr) {
  1666.         zoneSize = contigFree - kBytesReservedForToolboxInApplicationZone;
  1667.         zoneSize = zoneSize & ~0x003FF;
  1668.         if (zoneSize < (kBytesReservedForASLMInSubsidaryzone + kMinimumBytesForUsInSubsidiaryZone)) {
  1669.             err = memFullErr;
  1670.         }
  1671.     }
  1672.     
  1673.     // Allocate the memory for our zone and create a zone in that
  1674.     // block.  Then init ASLM, telling it to create a pool that
  1675.     // takes up the entire zone (minus the ASLM overhead factor)
  1676.     // in the current zone, ie the zone we just created.  Finally,
  1677.     // initialise OT.  OT will see that we've inited ASLM and use
  1678.     // the pool that ASLM created (in the zone we created) for
  1679.     // satisfying OTAllocMem requests.
  1680.     
  1681.     if (err == noErr) {
  1682.         gSubsidiaryZone = NewPtr(zoneSize);
  1683.         OTAssert("InitApplication: Couldn't get the memory but preflight says its there", gSubsidiaryZone != nil);
  1684.         OTAssert("InitApplication: Just being paranoid", MemError() == noErr);
  1685.  
  1686.         oldZone = GetZone();
  1687.  
  1688.         InitZone(nil, 16, gSubsidiaryZone + zoneSize, gSubsidiaryZone);
  1689.         OTAssert("InitApplication: InitZone failed", MemError() == noErr);
  1690.  
  1691.         // InitZone sets the current zone to the newly created zone,
  1692.         // so I don't have to do it myself.
  1693.                 
  1694.         //err = InitLibraryManager(zoneSize - kBytesReservedForASLMInSubsidaryzone, kCurrentZone, kNormalMemory);
  1695.         //this call has dissapeared
  1696.         if (err == noErr) {
  1697.             err = InitOpenTransport();
  1698.             if (err != noErr) {
  1699.                 //CleanupLibraryManager();
  1700.                 //So Has this one
  1701.             }
  1702.         }
  1703.         
  1704.         SetZone(oldZone);
  1705.     }
  1706.  
  1707.     // This code is used to artificially consume all memory
  1708.     // available through OTAllocMem.  I used this to test
  1709.     // that the above strategy works.  It's commented out now
  1710.     // because I'm happy that code works, but I want the code
  1711.     // around just in case I need to look at this situation again.
  1712.     
  1713.     #define TestByExhaustingOTMemoryPool 0
  1714.     #if TestByExhaustingOTMemoryPool
  1715.         if (err == noErr) {
  1716.             void *junk;
  1717.             
  1718.             do {
  1719.                 junk = OTAllocMem(55);
  1720.             } while (junk != nil);
  1721.             Debugger();
  1722.         }
  1723.     #endif
  1724.     
  1725.     return err;
  1726. }
  1727.     
  1728. static OSStatus InitApplication(void)
  1729.     // Initialises the application-specific stuff.
  1730. {
  1731.     OSStatus err;
  1732.     OSStatus junk;
  1733.     Handle mbarH;
  1734.     MenuHandle menuH;
  1735.     Rect viewRect;
  1736.     Rect dataBounds;
  1737.     Cell cellSize;
  1738.     
  1739.     // Initialise the Internet Config libraries that we use.  Note
  1740.     // that these are different from our connection to the Internet
  1741.     // Config extension, which is setup in RestorePreferences.  This
  1742.     // stuff is source code from IC (which is public domain, yay!) that
  1743.     // we've compiled and linked into our application for doing things
  1744.     // like managing List Manager lists, driving dialogs, etc.
  1745.     
  1746.     InitICDialogs();
  1747.     InitListManagerMiscSubs();
  1748.     
  1749.     // Setup the menu bar.
  1750.     
  1751.     mbarH = GetNewMBar(rMenuBar);
  1752.     OTAssert("InitApplication: Could not get menu bar", mbarH != nil);
  1753.     SetMenuBar(mbarH);
  1754.     InvalMenuBar();
  1755.     
  1756.     // Setup the Apple menu.
  1757.     
  1758.     menuH = GetMenuHandle(mApple);
  1759.     OTAssert("InitApplication: Could not get Apple menu", menuH != nil);
  1760.     AppendResMenu(menuH, 'DRVR');
  1761.     
  1762.     // Install our AppleEvent handlers.
  1763.     
  1764.     junk = AEInstallEventHandler(kCoreEventClass, kAEOpenApplication, NewAEEventHandlerProc(AEOpenApplicationHandler), 0, false);
  1765.     OTAssert("InitApplication: Could not install event handler", junk == noErr);
  1766.     junk = AEInstallEventHandler(kCoreEventClass, kAEOpenDocuments, NewAEEventHandlerProc(AEOpenDocumentsHandler), 0, false);
  1767.     OTAssert("InitApplication: Could not install event handler", junk == noErr);
  1768.     junk = AEInstallEventHandler(kCoreEventClass, kAEQuitApplication, NewAEEventHandlerProc(AEQuitApplicationHandler), 0, false);
  1769.     OTAssert("InitApplication: Could not install event handler", junk == noErr);
  1770.  
  1771.     // Create the main window.
  1772.         
  1773.     gMainWindow = GetNewDialog(rMainDialog, nil, (WindowPtr) -1L);
  1774.     OTAssert("InitApplication: Could not create main window", gMainWindow != nil);
  1775.  
  1776.     gMainWindowGrowBounds.top = gMainWindow->portRect.bottom;
  1777.     gMainWindowGrowBounds.left = gMainWindow->portRect.right;
  1778.     gMainWindowGrowBounds.bottom = qd.screenBits.bounds.bottom;
  1779.     gMainWindowGrowBounds.right = qd.screenBits.bounds.right;
  1780.     
  1781.     // And the list that's in the main window.
  1782.     
  1783.     gListDefProcUPP = NewListDefProc(ListDefProc);
  1784.     OTAssert("InitApplication: Could not create UPP for ListDefProc", gListDefProcUPP != nil);
  1785.     
  1786.     GetDItemRect(gMainWindow, ditLogEntryList, &viewRect);
  1787.     viewRect.bottom -= 15;
  1788.     viewRect.right -= 15;
  1789.     SetRect(&dataBounds, 0, 0, 1, 0);
  1790.     // SetPt(&cellSize, viewRect.right - viewRect.left, 16);
  1791.     SetPt(&cellSize, 1000, 16);
  1792.     gLogList = LNew(&viewRect,            // rView
  1793.                     &dataBounds,        // dataBounds
  1794.                     cellSize,            // cSize
  1795.                     rSimpleLDEF,        // theProc
  1796.                     gMainWindow,        // theWindow
  1797.                     true,                // drawIt
  1798.                     true,                // hasGrow
  1799.                     true,                // scrollHoriz
  1800.                     true);                // scrollVert
  1801.     OTAssert("InitApplication: Could not create ListHandle", gLogList != nil);
  1802.     (**gLogList).refCon = (long) gListDefProcUPP;
  1803.     
  1804.     gListUserItemUpdateUPP = NewUserItemProc(ListUserItemUpdateProc);
  1805.     OTAssert("InitApplication: Could not create UPP for ListUserItemUpdateProc", gListUserItemUpdateUPP != nil);
  1806.  
  1807.     SetDItemHandle(gMainWindow, ditLogEntryList, (Handle) gListUserItemUpdateUPP);
  1808.     
  1809.     // Restore our saved preferences, or setup defaults if
  1810.     // we have none.
  1811.     
  1812.     RestorePreferences();
  1813.     
  1814.     // Sync the Start and Stop buttons with the state of
  1815.     // the back end.
  1816.     
  1817.     UpdateStartStopUI();
  1818.     
  1819.     // Startup Open Transport.  We have to do some special
  1820.     // things to prevent OT eating our entire heap.  See
  1821.     // the comments associated with this routine.
  1822.     
  1823.     err = InitOpenTransportWithMemoryLimit();
  1824.  
  1825.     return err;
  1826. }
  1827.  
  1828. static void TermApplication(void)
  1829.     // Shut down our application.  This is normal quitting,
  1830.     // ie via AppleEvent or via menu command.  The main event
  1831.     // loop has already shut down the logging stuff.  All we
  1832.     // need to do is close down the connection to Open Transport,
  1833.     // ASLM and Internet Config.
  1834.     //
  1835.     // Note that we make no attempt to clean up our toolbox stuff
  1836.     // or memory allocates.  The Process Manager will do that for us
  1837.     // and there's no point writing extra code (and extra bugs) to
  1838.     // duplicate that here.
  1839. {
  1840.     CloseOpenTransport();
  1841.     //CleanupLibraryManager();
  1842.     TermPreferences();
  1843.     
  1844.     OTAssert("TermApplication: Quitting with raw streams open!!!", ! LoggingActive() );
  1845. }
  1846.  
  1847. #if GENERATINGCFM
  1848.  
  1849.     // Raw streams are not tracked by Open Transport.  When the
  1850.     // application quits abnormally, we have to make sure that
  1851.     // we have closed the stream, otherwise it will dangle around
  1852.     // in memory. This would not be too bad except:
  1853.     //
  1854.     // a) the notifier may be called, calling code that is no
  1855.     //    no longer loaded, and
  1856.     // b) the log driver won't let another client hook up as
  1857.     //    the stream logger as long as the stream is open.
  1858.     //
  1859.     // So we have a CFM terminate procedure that shuts down
  1860.     // the raw stream if it's open.
  1861.     //
  1862.     // Note that I don't recommend this approach for closing
  1863.     // endpoints and such.  OT has automatic shutdown code,
  1864.     // and I recommend that you rely on it for emergency shutdown
  1865.     // in the general case.  This code is only necessary because
  1866.     // we're using raw streams.
  1867.     //
  1868.     // Of course, for normal shutdown, ie the user chooses Quit, you
  1869.     // should not rely on OT's emergency shutdown support; you
  1870.     // should always call CloseOpenTransport yourself for normal
  1871.     // shutdown.
  1872.  
  1873.     extern pascal void EmergencyShutdown(void);
  1874.  
  1875.     extern pascal void EmergencyShutdown(void)
  1876.     {
  1877.         if ( LoggingActive() ) {
  1878.             OTDebugStr("EmergencyShutdown: Had to StopLogging");
  1879.             StopLogging();
  1880.         }
  1881.     }
  1882.  
  1883. #else
  1884.  
  1885.     // I could do the equivalent for the 68K case but it's not as
  1886.     // easy and I don't expect there to be many people using this
  1887.     // application on a 68K.
  1888.  
  1889. #endif
  1890.  
  1891. /////////////////////////////////////////////////////////////////////
  1892. #pragma mark **** The Main Line! *****
  1893.  
  1894. extern void main(void)
  1895. {
  1896.     OSStatus err;
  1897.  
  1898.     // We make lots of calls to the OT raw streams API, which
  1899.     // is not available to emulated code on the PPC.  So if we're
  1900.     // generating 68K code, we insert a check to prevent us
  1901.     // running emulated.
  1902.     
  1903.     #if ! GENERATINGCFM
  1904.         {
  1905.             SInt32 response;
  1906.             
  1907.             if ( Gestalt(gestaltSysArchitecture, &response) == noErr &&
  1908.                     response == gestaltPowerPC ) {
  1909.                 OTDebugStr("main: No sneaky running 68K on PPC!");
  1910.                 ExitToShell();
  1911.             }
  1912.         }
  1913.     #endif
  1914.     
  1915.     InitMacToolbox();
  1916.     err = InitApplication();
  1917.     OTAssert("main: InitApplication returned an error", err == noErr);
  1918.     if (err == noErr) {
  1919.     
  1920.         MainEventLoop();
  1921.         
  1922.         TermApplication();
  1923.     }
  1924. }
  1925.